package it.traveldream.tests;

import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_DRIVER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_PASSWORD;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_URL;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_USER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_LEVEL;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_SESSION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_THREAD;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_TIMESTAMP;
import static org.eclipse.persistence.config.PersistenceUnitProperties.TARGET_SERVER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.TRANSACTION_TYPE;
import it.traveldream.dto.HotelDTO.HotelRoomType;
import it.traveldream.entities.BookedExcursion;
import it.traveldream.entities.BookedHotel;
import it.traveldream.entities.BookedMean;
import it.traveldream.entities.Customer;
import it.traveldream.entities.Departure;
import it.traveldream.entities.Excursion;
import it.traveldream.entities.Hotel;
import it.traveldream.entities.HotelRoom;
import it.traveldream.entities.Location;
import it.traveldream.entities.MeanType;
import it.traveldream.entities.Product;
import it.traveldream.entities.ProductType;
import it.traveldream.entities.Reservation;
import it.traveldream.entities.TransportMean;
import it.traveldream.entities.TravelPackage;
import it.traveldream.entities.User;
import it.traveldream.entities.UserType;
import it.traveldream.utils.BookedMeanDescriptor;
import it.traveldream.utils.BookedRoomDescriptor;
import it.traveldream.utils.CalendarUtils.WeekDay;
import it.traveldream.utils.DigestUtils;
import it.traveldream.utils.JavaBeanResult;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.spi.PersistenceUnitTransactionType;

import org.eclipse.persistence.config.TargetServer;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class EntitiesUnitTest {

	private static EntityManager em = null;
	
	private static Long GOING_FLIGHT = 2l;
	private static Long BACK_FLIGHT = 3l;
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		Map<String, String> properties = new HashMap<String, String>();
		 
	    // Ensure RESOURCE_LOCAL transactions is used.
	    properties.put(TRANSACTION_TYPE,
	        PersistenceUnitTransactionType.RESOURCE_LOCAL.name());
	 
	    // Configure the internal EclipseLink connection pool
	    properties.put(JDBC_DRIVER, "com.mysql.jdbc.Driver");
	    properties.put(JDBC_URL, "jdbc:mysql://localhost:3306/TraveldreamDB");
	    properties.put(JDBC_USER, "traveldream");
	    properties.put(JDBC_PASSWORD, "traveldream");
	 
	    // Configure logging. FINE ensures all SQL is shown
	    properties.put(LOGGING_LEVEL, "FINE");
	    properties.put(LOGGING_TIMESTAMP, "false");
	    properties.put(LOGGING_THREAD, "false");
	    properties.put(LOGGING_SESSION, "false");
	 
	    // Ensure that no server-platform is configured
	    properties.put(TARGET_SERVER, TargetServer.None);
		if (em == null) {
            em = (EntityManager) Persistence.createEntityManagerFactory("traveldreamEJB", properties).createEntityManager();
        }
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
	}

	@Before
	public void setUp() throws Exception {
		
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void testUserEntity( ) throws Exception {
		// Start a transaction
        em.getTransaction().begin();
        // ------------
        
        User e2 = new Customer();
        e2.setUserName("diehard");
        e2.setPwdHash(DigestUtils.sha512Hex("diehard"));  
        e2.setMail("die@die.com");
        UserType c = em.find(UserType.class, "Customer");
        c.getUsers().add(e2);
        
        em.persist(e2);
        em.merge(c);
        em.flush();
        // ------------
        // Commit the Transaction
        em.getTransaction().commit();
	}

	@Test
	public void testProductEntity(){
		// Start a transaction
        em.getTransaction().begin();
        // ------------
        
        Product p = new Excursion();
        p.setName("primaescursione");
        p.setLocation(em.find(Location.class, 2l));
        
        Excursion e = ((Excursion)p);
        e.setDuration(5);
        e.setWeekDay(WeekDay.MONDAY);
        em.persist(e);
        
        int nProducts = em.createNamedQuery("getAllExcursionsInLocation").
        		setParameter("location", em.find(Location.class, 2l)).
        		getResultList().size();
        
        Excursion e2 = new Excursion();
        e2.setName("secondaescursione");
        e2.setLocation(em.find(Location.class, 2l));
        e2.setDuration(5);
        e2.setWeekDay(WeekDay.FRIDAY);
        em.persist(e2);
        
        int nProducts2 = em.createNamedQuery("getAllExcursionsInLocation").
        		setParameter("location", em.find(Location.class, 2l)).
        		getResultList().size();
        Assert.assertTrue(nProducts2 == nProducts + 1);
        
        e2.setName("secondaescursioneASD");
        em.merge(e2);
        
        int nProducts3 = em.createNamedQuery("getAllExcursionsInLocation").
        		setParameter("location", em.find(Location.class, 2l)).
        		getResultList().size();
        Assert.assertTrue(nProducts2 == nProducts3);
        
        em.remove(e2);
        nProducts3 = em.createNamedQuery("getAllExcursionsInLocation").
        		setParameter("location", em.find(Location.class, 2l)).
        		getResultList().size();
        Assert.assertTrue(nProducts3 == nProducts);
        
        Excursion e3 = new Excursion();
        e3.setName("terzaescursione");
        e3.setLocation(em.find(Location.class, 1l));
        e3.setDuration(5);
        e3.setWeekDay(WeekDay.THURSDAY);
        em.persist(e3);
        
        TransportMean tm = new TransportMean();
        tm.setName("primovolo");
        tm.setLocation(em.find(Location.class, 2l));
        tm.setMeanType(em.find(MeanType.class, GOING_FLIGHT));
        tm.setDeparture(em.find(Departure.class, 3l));
        tm.setPrice(new BigDecimal("100"));
        tm.setSpaces(4);
        em.persist(tm);
        TransportMean backTm = new TransportMean();
        backTm.setName("primovolo");
        backTm.setLocation(em.find(Location.class, 2l));
        backTm.setMeanType(em.find(MeanType.class, BACK_FLIGHT));
        backTm.setDeparture(em.find(Departure.class, 3l));
        backTm.setPrice(new BigDecimal("100"));
        backTm.setSpaces(4);
        em.persist(backTm);
        
        TransportMean tm2 = new TransportMean();
        tm2.setName("secondovolo");
        tm2.setLocation(em.find(Location.class, 1l));
        tm2.setMeanType(em.find(MeanType.class, GOING_FLIGHT));
        tm2.setDeparture(em.find(Departure.class, 3l));
        tm2.setPrice(new BigDecimal("100"));
        tm2.setSpaces(5);
        em.persist(tm2);
        TransportMean backTm2 = new TransportMean();
        backTm2.setName("primovolo");
        backTm2.setLocation(em.find(Location.class, 1l));
        backTm2.setMeanType(em.find(MeanType.class, BACK_FLIGHT));
        backTm2.setDeparture(em.find(Departure.class, 3l));
        backTm2.setPrice(new BigDecimal("100"));
        backTm2.setSpaces(4);
        em.persist(backTm2);
        
        Hotel h = new Hotel();
        h.setName("primohotel");
        h.setLocation(em.find(Location.class, 2l));
        HashMap<HotelRoomType, HotelRoom> rooms = new HashMap<>();
        rooms.put(HotelRoomType.SINGLE_ROOM, new HotelRoom(new BigDecimal(50), 25));
        rooms.put(HotelRoomType.DOUBLE_ROOM, new HotelRoom(new BigDecimal(40), 15));
        h.setHotelRooms(rooms);
        em.persist(h);
      
        
        Hotel h2 = new Hotel();
        h2.setName("secondohotel");
        h2.setLocation(em.find(Location.class, 1l));
        rooms = new HashMap<>();
        rooms.put(HotelRoomType.SINGLE_ROOM, new HotelRoom(new BigDecimal(50), 25));
        rooms.put(HotelRoomType.DOUBLE_ROOM, new HotelRoom(new BigDecimal(40), 15));
        h2.setHotelRooms(rooms);
        em.persist(h2);
        
        em.flush();
        
        Assert.assertTrue(h2.getProductId() != 0);
        
        // ------------
        // Commit the Transaction
        em.getTransaction().commit();         
	}
	
	@Test
	@SuppressWarnings("rawtypes")
	public void testPackageEntity(){
		
		em.getTransaction().begin();
       
		TravelPackage tp = new TravelPackage();
		Location myLocation = em.find(Location.class, 2L);
        
		List products = 
        		em.createNamedQuery("getAllProductInLocation").
        		setParameter("location", myLocation).getResultList();
		
        Iterator i = products.iterator();
        while (i.hasNext()){
        	Object[] row = (Object[])i.next();
        	
        	Product p = (Product) row[0];
        	String type = ((ProductType) row[1]).getProductTypeId().toString();
        	if (type.equals(ProductType.TRANSPORTMEAN)){
				TransportMean m = em.find(TransportMean.class, p.getProductId());
				tp.setMean(m);
			} else if (type.equals(ProductType.HOTEL)){
				Hotel h = em.find(Hotel.class, p.getProductId());
				tp.setHotel(h);
			} else if (type.equals(ProductType.EXCURSION)){
				Excursion e = em.find(Excursion.class, p.getProductId());
				List<Excursion> exs = new ArrayList<Excursion>();
				exs.add(e);
				tp.setExcursions(exs);
			}
        	
        }
        
        em.persist(tp);
        // ------------
        // Commit the Transaction
        em.getTransaction().commit();
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testReservationEntity() throws Exception{
		em.getTransaction().begin();
        
        Reservation r = new Reservation();
        Reservation r2 = new Reservation();
        Reservation r3 = new Reservation();
        Location myLocation = em.find(Location.class, 2L);
        Location myLocation2 = em.find(Location.class, 1L);
        
        List<TravelPackage> tps = 
        		em.createNamedQuery("getAllTPInLocation").
        		setParameter("location", myLocation).
        		getResultList();
        List<TransportMean> tms = 
        		em.createNamedQuery("getAllMeanToLocation").
        		setParameter("location", myLocation2).
        		getResultList();
        List<Hotel> hts =
        		em.createNamedQuery("getAllHotelsInLocation").
        		setParameter("location", myLocation2).
        		getResultList();
        
        Assert.assertNotNull(tps.get(0));
        Assert.assertNotNull(tms.get(0));
        Assert.assertNotNull(hts.get(0));
        
        Customer e2 = em.find(Customer.class, "rambo");
        if (e2 == null){
        	e2 = new Customer();
        	e2.setUserName("rambo");
            e2.setPwdHash(DigestUtils.sha512Hex("rambo"));
            
            UserType c = em.find(UserType.class, "Customer");
            c.getUsers().add(e2);
            
            
            em.persist(e2);
            em.merge(c);
            em.flush();
        }
        Customer e3 = em.find(Customer.class, "terminator");
        if (e3 == null){
        	e3 = new Customer();
        	e3.setUserName("terminator");
            e3.setPwdHash(DigestUtils.sha512Hex("terminator"));
            
            UserType c = em.find(UserType.class, "Customer");
            c.getUsers().add(e3);	
            
            em.persist(e3);
            em.merge(c);
            em.flush();
        }
        
        // FIRST RESERVATION
        TravelPackage tp = tps.get(0);
        r.setCustomer(e2);
        Calendar march26 = Calendar.getInstance();
        march26.set(2013, 2, 26);
        r.setLeavingDate(march26.getTime());
        Calendar april26 = Calendar.getInstance();
        april26.set(2013, 3, 26);
        r.setEndDate(april26.getTime());
        
        BookedMean bm = new BookedMean();
        bm.setProduct(tp.getMean());
        bm.setQuantity(2);
        BookedMean bmBack = new BookedMean();
        TransportMean back = (TransportMean) em.createNamedQuery("getExistingBackMeanOfTypeToLocation").
        		setParameter("type", em.find(MeanType.class, BACK_FLIGHT)).
        		setParameter("location", tp.getMean().getLocation()).
        		getResultList().get(0);
        bmBack.setProduct(back);
        bmBack.setQuantity(2);
        
        BookedHotel bh = new BookedHotel();
        bh.setProduct(tp.getHotel());
        Map<HotelRoomType, Integer> rooms = new HashMap<>();
        rooms.put(HotelRoomType.SINGLE_ROOM, 2);
        bh.setBookedRooms(rooms);
        
        List<BookedExcursion> bexs = new ArrayList<>();
        List<Excursion> exs = tp.getExcursions();
        
        for (Excursion e : exs){
        	BookedExcursion be = new BookedExcursion();
        	be.setProduct(e);
        	bexs.add(be);
        }
        
        r.setExcursions(bexs);
        r.setGoingMean(bm);
        r.setComingBackMean(bmBack);
        r.setHotel(bh);
        
        em.persist(r);
        
        // SECOND RESERVATION
        r2.setCustomer(e2);
        Calendar march24 = Calendar.getInstance();
        march24.set(2013, 2, 24);
        r2.setLeavingDate(march24.getTime());
        r2.setEndDate(april26.getTime());
        
        BookedMean bm2 = new BookedMean();
        bm2.setProduct(tp.getMean());
        bm2.setQuantity(2);
        r2.setGoingMean(bm2);
        BookedMean bm2Back = new BookedMean();
        bm2Back.setProduct(back);
        bm2Back.setQuantity(2);
        r2.setComingBackMean(bm2Back);
        
        BookedHotel bh2 = new BookedHotel();
        bh2.setProduct(tp.getHotel());
        rooms = new HashMap<>();
        rooms.put(HotelRoomType.SINGLE_ROOM, 2);
        bh2.setBookedRooms(rooms);
        r2.setHotel(bh2);
        
        em.persist(r2);
        
        // THIRD RESERVATION
        r3.setCustomer(e3);
        r3.setLeavingDate(march24.getTime());
        r3.setEndDate(april26.getTime());
        
        BookedMean bm3 = new BookedMean();
        bm3.setProduct(tms.get(0));
        bm3.setQuantity(2);
        r3.setGoingMean(bm3);
        TransportMean back2 = (TransportMean) em.createNamedQuery("getExistingBackMeanOfTypeToLocation").
        		setParameter("type", em.find(MeanType.class, BACK_FLIGHT)).
        		setParameter("location", tms.get(0).getLocation()).
        		getResultList().get(0);
        BookedMean bm3Back = new BookedMean();
        bm3Back.setProduct(back2);
        bm3Back.setQuantity(2);
        r3.setComingBackMean(bm3Back);
        
        BookedHotel bh3 = new BookedHotel();
        bh3.setProduct(hts.get(0));
        rooms = new HashMap<>();
        rooms.put(HotelRoomType.SINGLE_ROOM, 2);
        bh3.setBookedRooms(rooms);
        r3.setHotel(bh3);
        
        em.persist(r3);
        
        // ------------
        // Commit the Transaction
        em.getTransaction().commit();

        /**
         * Checking consistency of Reservations
         */
        
        // There are two left spaces for leaving dates...
        Calendar cal4 = Calendar.getInstance();
        cal4.set(2013, 1, 20);
		Date since = cal4.getTime();
		Calendar cal5 = Calendar.getInstance();
        cal5.set(2013, 6, 01);
        Date until = cal5.getTime();
		
        // controllo prenotazione voli
        // Volo di andata ha ancora 2 posti
        Query query = em.createNamedQuery("getLeavingMeanAvailabilityInDate").
        		setParameter(1, r.getGoingMean().getProduct().getProductId()).
        		setParameter(2, r.getLeavingDate());
        JavaBeanResult.setQueryResultClass(query, BookedMeanDescriptor.class);
        List<BookedMeanDescriptor> n = query.getResultList();
        Assert.assertTrue(n.get(0).getAvailability() == 2);
        
        // Volo di ritorno pieno
        Query query2 = em.createNamedQuery("getComingBackMeanAvailabilityInDate").
        		setParameter(1, r.getComingBackMean().getProduct().getProductId()).
        		setParameter(2, r.getEndDate());
        JavaBeanResult.setQueryResultClass(query2, BookedMeanDescriptor.class);
        n = query2.getResultList();
        Assert.assertTrue(n.get(0).getAvailability() == 0);
        
		// Hotel is properly booked
		Query query3 = em.createNamedQuery("getHotelsInLocationBookedRoomsBetweenDates").
				setParameter(1, r.getHotel().getProduct().getLocation().getId()).
				setParameter(2, since).
				setParameter(3, until);
		JavaBeanResult.setQueryResultClass(query3, BookedRoomDescriptor.class);
		List<BookedRoomDescriptor> n2 = query3.getResultList();
		Assert.assertTrue(n2.size() == 1);
	}
}
